Python之路 - 并发编程之协程
前言 🍀
在前面的文章中 , 基本已经可以解决并发编程中的基本问题了 , 但是如果我们要利用单线程来实现并发 , 线程是轻量级的进程 , 为了使计算机资源能更充分的利用 , 那么我们就需要用到协程了
并发的本质就是上下文切换加上保存状态 , 那么我们就可以想到关键字yield
, 我们在生成器篇章中 , 就是利用了yield
实现了状态的保存 , 来看一个廖大大的例子
生产者消费者模型yield版
1 | import time |
上述例子中yield
确实实现了并发 , 但是并没有实现遇到IO操作进行自动切换 , 所以协程出场了
协程 🍀
首先通过上述例子 , 我们知道 , 对于单线程下 , 我们不可避免程序中出现IO操作 , 但是如果我们能够在自己的程序中去实现这一步 , 线程可以最大限度地处于就绪态 , 相当于我们在用户程序级别将自己的IO操作最大限度地隐藏起来 , 这样线程的计算效率将会得到进一步的提升
协程(Coroutine) : 是单线程下的并发 , 又称微线程 , 纤程 . 协程是一种用户态的轻量级线程 , 即协程有用户自己控制调度
协程的本质就是在单线程下 , 由用户自己控制一个任务遇到IO阻塞了就切换另外一个任务去执行 , 以此来提升效率
在单线程内开启协程 , 一旦遇到IO , 就会从应用程序级别控制切换 , 非IO操作的切换与效率无关
使用协程的优缺点
优点 :
- 协程的切换开销更小 , 属于程序级别的切换 , 更加轻量级
- 单线程内就可以实现并发的效果 , 最大限度利用CPU
缺点 :
- 协程的本质是单线程下 , 无法利用多核 , 可以是一个程序开启多个进程 , 每个进程内开启多个线程 , 每个线程内开启协程
- 协程指的是单个线程 , 因而一旦协程出现阻塞 将会阻塞整个线程
Greenlet 🍀
我们在前面已经用yield
实现了协程 , 但是使用yield
需要先得到初始化一次的生成器 , 然后再调用send , 这无疑是非常麻烦的 , 所以我们可以使用greenlet
模块可以非常简单地实现协程
1 | import greenlet |
greenlet在没有IO的情况下或者没有重复开辟内存空间的操作下 , 反而会降低程序的执行速度 , 因为greenlet仅仅是单纯的切换 , 比如下面的例子
普通版本
1 | import time |
greenlet版本
1 | import greenlet |
greenlet只是提供了一种比generator
(yield)更加快捷的切换方式 , 当切到一个任务执行时如果遇到IO , 那就原地阻塞 , 仍然是没有解决遇到IO自动切换来提升效率的问题 , 所以为了真正的提高效率 , 我们就需要使用Gevent模块了
Gevent 🍀
Gevent是一个第三方库 , 可以通过gevent轻松实现并发同步或异步编程 , 在gevent中用到的主要模式是Greenlet , 它是以C扩展模块形式接入Python的轻量级协程
简单使用介绍
1 | # 在gevent库中,主要使用Greenlet模式 |
IO阻塞自动切换
1 | import gevent |
PS : 上例中gevent.sleep(2) 模拟的是gevent可以识别的IO阻塞 , 如果是不能直接识别的需要将from gevent import monkey ; monkey.patch_all()
放到文件的开头
Gevent同步与异步
1 | from gevent import spawn, joinall, monkey |
Gevent实例 🍀
爬虫
1 | from gevent import monkey |
socket并发
server.py
1 | from gevent import monkey |
client.py
1 | import socket |